#include	"stdafx.h"

namespace	nPrivate
{
	// Primary surface
	LPDIRECTDRAWSURFACE	PrimarySurface = NULL;
	LPDIRECTDRAWSURFACE	TempSurface = NULL;

	// Color shifts
	int		rshift;
	int		gshift;
	int		bshift;

	// Shift from mask calculator
	int GetMaskInfo (DWORD Bitmask, int* lpShift)
	{
		int Precision, Shift;

		Precision = Shift = 0;
		//count the zeros on right hand side
		while (!(Bitmask & 0x01L))
		{
			Bitmask >>= 1;
			Shift++;
		}

		//count the ones on right hand side
		while (Bitmask & 0x01L)
		{
			Bitmask >>= 1;
			Precision++;
		}
		*lpShift = Shift;
		return Precision;
	}

	void	BlitSurfaceToScreen(LPDIRECTDRAWSURFACE lpSurface)
	{
		HRESULT hr;

		PALETTEENTRY	palette[256];

		RECT rt;
		POINT p = {0, 0};
		ClientToScreen(g_hWnd, &p);
		rt.left   = 0 + p.x;
		rt.top    = 0 + p.y;

		// TODO: Fix me
		rt.right  = 640 + p.x;
		rt.bottom = 480 + p.y;

		// Lock buffers
		DDSURFACEDESC	srcDesc = {0};
		srcDesc.dwSize = sizeof(srcDesc);
		DDSURFACEDESC	dstDesc = {0};
		dstDesc.dwSize = sizeof(dstDesc);

		DDPIXELFORMAT DDpf = {0};
		int BytesPerPixel;
		DDpf.dwSize = sizeof(DDpf);
		TempSurface->GetPixelFormat(&DDpf);
		BytesPerPixel = DDpf.dwRGBBitCount / 8;

		g_pPalette->GetEntries(0, 0, 256, &palette[0]);

		lpSurface->Lock(NULL, &srcDesc, DDLOCK_READONLY, NULL);
		TempSurface->Lock(NULL, &dstDesc, DDLOCK_WRITEONLY, NULL);

		BYTE * srcData = (BYTE*)srcDesc.lpSurface;
		BYTE * dstData = (BYTE*)dstDesc.lpSurface;
		for (DWORD y1 = 0; y1 < srcDesc.dwHeight; y1++)
		{
			BYTE * srcLine = srcData;
			BYTE * dstLine = dstData;

			for (DWORD x1 = 0; x1 < srcDesc.dwWidth; ++x1)
			{
				tagPALETTEENTRY	& pal = palette[*(srcLine++)];

				DWORD	finalColor = 0;
				finalColor |= ((pal.peRed   << rshift) & DDpf.dwRBitMask);
				finalColor |= ((pal.peGreen << gshift) & DDpf.dwGBitMask);
				finalColor |= ((pal.peBlue  << bshift) & DDpf.dwBBitMask);

				BYTE * byteBuf = (BYTE *)&finalColor;

				for (int i = 0; i < BytesPerPixel; ++i)
					*(dstLine++) = byteBuf[i];
			}

			srcData += srcDesc.lPitch;
			dstData += dstDesc.lPitch;
		}

		TempSurface->Unlock(dstDesc.lpSurface);
		lpSurface->Unlock(srcDesc.lpSurface);

		do 
		{
			hr = PrimarySurface->Blt(&rt, TempSurface, NULL, DDBLT_WAIT, NULL);

			if (hr == DDERR_SURFACELOST)
				hr = RestoreSurfaces();
		} while(hr == DDERR_WASSTILLDRAWING);

		if (lpSurface != g_lpPrimarySurface)
		{
			do 
			{
				hr = g_lpPrimarySurface->Blt(NULL, lpSurface, NULL, DDBLT_WAIT, NULL);

				if (hr == DDERR_SURFACELOST)
					hr = RestoreSurfaces();
			} while(hr == DDERR_WASSTILLDRAWING);
		}
	}
}

// Hooked CreateDirectDraw function
HWND	CreateDirectDraw(DWORD videoMode)
{
	MEMORYSTATUS	memStatus;
	GlobalMemoryStatus(&memStatus);
	g_globalAvailableMemory = memStatus.dwAvailPhys;

	// No idea what this doing.
	dword_578E20 = &byte_579350;

	if (FAILED(DirectDrawCreate(NULL, &g_pDirectDraw, NULL)))
	{
		QuitCleanup();

		DestroyWindow(g_hWnd);
		g_hWnd = 0;

		return (HWND)0;
	}

	if (nConfig::Render_FullScreen)
	{
		g_pDirectDraw->SetCooperativeLevel(g_hWnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);
	} else
	{
		g_pDirectDraw->SetCooperativeLevel(g_hWnd, DDSCL_NORMAL);
	}

	// Choose screen resolution
	switch (videoMode)
	{
	case 0:
		g_screen_res_x = 320;
		g_screen_res_y = 200;
		break;
	case 2:
		g_screen_res_x = 320;
		g_screen_res_y = 240;
		break;
	case 4:
		g_screen_res_x = 640;
		g_screen_res_y = 480;
		break;
	}

	g_screen_stride = g_screen_res_x * g_screen_res_y;
	sub_528740(g_screen_stride, g_screen_res_y);

	// Huh!?
	word_57931C = 0;

	DDSURFACEDESC	desc = {0};
	desc.dwSize = sizeof(desc);

	if (nConfig::Render_FullScreen)
	{
		g_pDirectDraw->SetDisplayMode(g_screen_res_x, g_screen_res_y, 8);

		// For full screen mode create primary surface with 1 backbuffer
		desc.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
		desc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX;
		desc.dwBackBufferCount = 1;
	} else
	{
		// Create windowed primary surface
		desc.dwFlags = DDSD_CAPS;
		desc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
		g_pDirectDraw->CreateSurface(&desc, &nPrivate::PrimarySurface, NULL);

		// Create "fake" primary surface for MOO2 with 8 bit palette
		memset(&desc, 0, sizeof(desc));
		desc.dwSize = sizeof(desc);
		desc.dwWidth  = g_screen_res_x;
		desc.dwHeight = g_screen_res_y;
		desc.dwFlags  = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;
		desc.ddsCaps.dwCaps = DDSCAPS_SYSTEMMEMORY | DDSCAPS_OFFSCREENPLAIN;
		desc.ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT);
		desc.ddpfPixelFormat.dwFlags = DDPF_PALETTEINDEXED8 | DDPF_RGB;
		desc.ddpfPixelFormat.dwRGBBitCount = 8;
	}

	if (g_pDirectDraw->CreateSurface(&desc, &g_lpPrimarySurface, NULL) != DD_OK)
	{
		QuitCleanup();

		DestroyWindow(g_hWnd);
		g_hWnd = 0;

		return (HWND)0;
	}

	if (!nConfig::Render_FullScreen)
	{
		// Set clipper for primary surface
		LPDIRECTDRAWCLIPPER	pcClipper;
		g_pDirectDraw->CreateClipper(0, &pcClipper, NULL);
		pcClipper->SetHWnd(0, g_hWnd);
		nPrivate::PrimarySurface->SetClipper(pcClipper);

		// Create additional surface with primary surface properties
		DDSURFACEDESC	desc2 = {0};
		desc2.dwSize = sizeof(desc2);
		desc2.dwWidth  = g_screen_res_x;
		desc2.dwHeight = g_screen_res_y;
		desc2.dwFlags  = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;
		desc2.ddsCaps.dwCaps = DDSCAPS_SYSTEMMEMORY | DDSCAPS_OFFSCREENPLAIN;

		DDPIXELFORMAT	pFormat = {0};
		pFormat.dwSize = sizeof(pFormat);
		nPrivate::PrimarySurface->GetPixelFormat(&pFormat);

		desc2.ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT);
		desc2.ddpfPixelFormat.dwFlags = DDPF_RGB;
		desc2.ddpfPixelFormat.dwRGBBitCount = pFormat.dwRGBBitCount;
		desc2.ddpfPixelFormat.dwRBitMask = pFormat.dwRBitMask;
		desc2.ddpfPixelFormat.dwGBitMask = pFormat.dwGBitMask;
		desc2.ddpfPixelFormat.dwBBitMask = pFormat.dwBBitMask;

		g_pDirectDraw->CreateSurface(&desc2, &nPrivate::TempSurface, NULL);
		if (nPrivate::TempSurface == NULL)
		{
			QuitCleanup();

			DestroyWindow(g_hWnd);
			g_hWnd = 0;

			return (HWND)0;
		}

		// Get color information
		nPrivate::GetMaskInfo(desc2.ddpfPixelFormat.dwRBitMask, &nPrivate::rshift);
		nPrivate::GetMaskInfo(desc2.ddpfPixelFormat.dwGBitMask, &nPrivate::gshift);
		nPrivate::GetMaskInfo(desc2.ddpfPixelFormat.dwBBitMask, &nPrivate::bshift);
	}

	memset(&desc, 0, sizeof(desc));
	desc.dwSize = sizeof(desc);
	desc.dwWidth  = g_screen_res_x;
	desc.dwHeight = g_screen_res_y;
	desc.dwFlags  = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;
	desc.ddsCaps.dwCaps = DDSCAPS_SYSTEMMEMORY | DDSCAPS_OFFSCREENPLAIN;
	desc.ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT);
	desc.ddpfPixelFormat.dwFlags = DDPF_PALETTEINDEXED8 | DDPF_RGB;
	desc.ddpfPixelFormat.dwRGBBitCount = 8;

	if (nConfig::Render_FullScreen)
	{
		DDSCAPS	caps;
		caps.dwCaps = DDSCAPS_BACKBUFFER;

		// TODO: Error checking
		g_lpPrimarySurface->GetAttachedSurface(&caps, &g_lpBackBufferSurface);
	} else
	{
		// Create back buffer surface (fake)
		g_pDirectDraw->CreateSurface(&desc, &g_lpBackBufferSurface, NULL);
		if (g_lpBackBufferSurface == NULL)
		{
			QuitCleanup();

			DestroyWindow(g_hWnd);
			g_hWnd = 0;

			return (HWND)0;
		}
	}

	// Create first buffer surface
	g_pDirectDraw->CreateSurface(&desc, &g_lpSurface1, NULL);
	if (g_lpSurface1 == NULL)
	{
		QuitCleanup();

		DestroyWindow(g_hWnd);
		g_hWnd = 0;

		return (HWND)0;
	}

	// Create second buffer surface
	g_pDirectDraw->CreateSurface(&desc, &g_lpSurface2, NULL);
	if (g_lpSurface2 == NULL)
	{
		QuitCleanup();

		DestroyWindow(g_hWnd);
		g_hWnd = 0;

		return (HWND)0;
	}

	if (g_pPalette != NULL)
	{
		g_pPalette->Release();
		g_pPalette = NULL;
	}

	// Create palette
	if (g_pDirectDraw->CreatePalette(0x44, &g_pGlobalPalette, &g_pPalette, NULL) != DD_OK)
	{
		QuitCleanup();

		DestroyWindow(g_hWnd);
		g_hWnd = 0;

		return (HWND)0;
	}

	// Set palette
	if (nConfig::Render_FullScreen)
		g_lpPrimarySurface->SetPalette(g_pPalette);

	g_error_status = 0;

	// Huh?
	video_Set_Page_Off();
	graphics_fill(0, 0, 639, 479, 0);

	video_Set_Page_Back();
	graphics_fill(0, 0, 639, 479, 0);

	// Huh #5&6?
	sub_5287A0();
	sub_5287C0();

	// Huh #7?
	dword_55B1B4 = 0;

	graphics_Reset_Window();

	return g_hWnd;
}

// Hooker FlipSurface function
void	FlipSurface()
{
	if (nConfig::Render_FullScreen)
	{
		HRESULT hr;
		do 
		{
			hr = g_lpPrimarySurface->Flip(NULL, 0);

			if (hr == DDERR_SURFACELOST)
				hr = RestoreSurfaces();
		} while(hr == DDERR_WASSTILLDRAWING);
	} else
	{
		nPrivate::BlitSurfaceToScreen(g_lpBackBufferSurface);
	}

	FrameCount++;
}

// UnlockAppropriateRenderBuffer function
HRESULT	UnlockAppropriateRenderBuffer()
{
	HRESULT hr;

	switch (bRenderBuffer)
	{
	case 0:
		hr = g_lpPrimarySurface->Unlock(NULL);

		if (!nConfig::Render_FullScreen)
			nPrivate::BlitSurfaceToScreen(g_lpPrimarySurface);

		break;
	case 1:
		hr = g_lpSurface1->Unlock(NULL);
		break;
	case 2:
		hr = g_lpSurface2->Unlock(NULL);
		break;
	}

	// TODO: Error checking

	return hr;
}

// Primary surface unlocker
void	UnlockPrimarySurface()
{
	g_lpPrimarySurface->Unlock(NULL);

	if (!nConfig::Render_FullScreen)
		nPrivate::BlitSurfaceToScreen(g_lpPrimarySurface);
}

void	BlitToBackbuffer()
{
	RECT	rect;
	rect.left = 0;
	rect.top = 0;
	rect.right = g_screen_res_x;
	rect.bottom = g_screen_res_y;

	while (g_lpBackBufferSurface->BltFast(0, 0, g_lpSurface1, &rect, DDBLTFAST_WAIT) != DD_OK);
}

void	DrawCursorXY(i16 x, i16 y)
{
	if (word_578AC4 > 0)
	{
		if (word_578AC4 != dword_578B3C)
		{
			sub_526F80();
			dword_578B3C = word_578AC4;
		}

		RECT rect;
		rect.left = 0;
		rect.top  = 0;

		if (x + 24 >= g_screen_res_x - 1)
			rect.right = g_screen_res_x - x - 1;
		else
			rect.right = 24;

		if (y + 24 >= g_screen_res_y - 1)
			rect.bottom = g_screen_res_y - y - 1;
		else
			rect.bottom = 24;

		DDCOLORKEY	key;
		key.dwColorSpaceHighValue = 0;
		key.dwColorSpaceLowValue  = 0;
		g_pCursorSurface1->SetColorKey(8, &key);

		g_lpPrimarySurface->BltFast(x, y, g_pCursorSurface1, &rect, DDBLTFAST_WAIT | DDBLTFAST_SRCCOLORKEY);

		if (!nConfig::Render_FullScreen)
			nPrivate::BlitSurfaceToScreen(g_lpPrimarySurface);
	}
}

void	DrawAutoCursor()
{
	RECT rect;
	rect.left = 0;
	rect.top  = 0;

	if (cached_mouse_x + 24 >= g_screen_res_x - 1)
		rect.right = g_screen_res_x - cached_mouse_x - 1;
	else
		rect.right = 24;

	if (cached_mouse_y + 24 >= g_screen_res_y - 1)
		rect.bottom = g_screen_res_y - cached_mouse_y - 1;
	else
		rect.bottom = 24;

	g_lpPrimarySurface->BltFast(cached_mouse_x, cached_mouse_y, g_pCursorSurface2, &rect, DDBLTFAST_WAIT);

	if (!nConfig::Render_FullScreen)
		nPrivate::BlitSurfaceToScreen(g_lpPrimarySurface);
}

void	CreateCursorSurfaces()
{
	DDSURFACEDESC	desc2 = {0};
	desc2.dwSize = sizeof(desc2);
	desc2.dwWidth  = 24;
	desc2.dwHeight = 24;
	desc2.dwFlags  = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;
	desc2.ddsCaps.dwCaps = DDSCAPS_SYSTEMMEMORY | DDSCAPS_OFFSCREENPLAIN;
	desc2.ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT);
	desc2.ddpfPixelFormat.dwFlags = DDPF_PALETTEINDEXED8 | DDPF_RGB;
	desc2.ddpfPixelFormat.dwRGBBitCount = 8;

	g_pDirectDraw->CreateSurface(&desc2, &g_pCursorSurface1, NULL);

	g_pDirectDraw->CreateSurface(&desc2, &g_pCursorSurface2, NULL);
}

HRESULT	RestoreSurfaces()
{
	HRESULT hr = S_OK;

	if (g_lpPrimarySurface != NULL)
		g_lpPrimarySurface->Restore();

	if (g_lpBackBufferSurface != NULL)
		g_lpBackBufferSurface->Restore();

	if (g_lpSurface1 != NULL)
		g_lpSurface1->Restore();

	if (g_lpSurface2 != NULL)
		g_lpSurface2->Restore();

	if (g_pCursorSurface1 != NULL)
		g_pCursorSurface1->Restore();

	if (g_pCursorSurface2 != NULL)
		hr = g_pCursorSurface2->Restore();

	if (nPrivate::PrimarySurface != NULL)
		nPrivate::PrimarySurface->Restore();

	if (nPrivate::TempSurface != NULL)
		nPrivate::TempSurface->Restore();

	FrameCount = 0;
	if (dword_55B1B4 == 3)
	{
		video_Set_Refresh_Stencil();
	}

	return hr;
}

void	CleanupSurfaces()
{
	if (g_pDirectDraw != NULL)
	{
		if (g_lpPrimarySurface != NULL)
		{
			g_lpPrimarySurface->Release();
			g_lpPrimarySurface = NULL;
		}

		if (g_lpSurface1 != NULL)
		{
			g_lpSurface1->Release();
			g_lpSurface1 = NULL;
		}

		if (g_lpSurface2 != NULL)
		{
			g_lpSurface2->Release();
			g_lpSurface2 = NULL;
		}

		if (g_pPalette != NULL)
		{
			g_pPalette->Release();
			g_pPalette = NULL;
		}

		if (g_pCursorSurface1 != NULL)
		{
			g_pCursorSurface1->Release();
			g_pCursorSurface1 = NULL;
		}

		if (g_pCursorSurface2 != NULL)
		{
			g_pCursorSurface2->Release();
			g_pCursorSurface2 = NULL;
		}

		if (nPrivate::PrimarySurface != NULL)
		{
			nPrivate::PrimarySurface->Release();
			nPrivate::PrimarySurface = NULL;
		}

		if (nPrivate::TempSurface != NULL)
		{
			nPrivate::TempSurface->Release();
			nPrivate::TempSurface = NULL;
		}

		g_pDirectDraw->Release();
		g_pDirectDraw = NULL;
	}
}

void	ForceCursorPos(i16 x, i16 y)
{
	MouseX = x;
	MouseY = y;

	if (!nConfig::Render_FullScreen)
	{
		// Quick hack to ignore window size
		POINT p = {0, 0};
		ClientToScreen(g_hWnd, &p);

		x += (i16)p.x;
		y += (i16)p.y;
	}

	SetCursorPos(x, y);
}
//////////////////////////////////////////////////////////////////////////
const	char	*szClassName = "Microprose";

int __stdcall	WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd)
{
	ParseCommandLine(lpCmdLine);

	WNDCLASS	wndClass = {0};
	wndClass.cbClsExtra = 0;
	wndClass.cbWndExtra = 0;
	wndClass.hInstance = hInstance;
	wndClass.style = CS_VREDRAW | CS_HREDRAW;
	wndClass.lpfnWndProc = (WNDPROC)WndProc;
	wndClass.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(101));
	wndClass.hCursor = LoadCursor(hInstance, IDC_ARROW);
	wndClass.lpszMenuName = szClassName;
	wndClass.lpszClassName = szClassName;
	RegisterClass(&wndClass);

	HWND hWindow = FindWindow(szClassName, szClassName);
	if (hWindow != NULL)
	{
		MessageBox(0, "MOO II is already running.", szClassName, MB_ICONERROR);
		ExitProcess(0);
	}

	int width = 0;
	int height = 0;
	DWORD flags = 0;

	if (nConfig::Render_FullScreen)
	{
		width = GetSystemMetrics(SM_CXSCREEN);
		height= GetSystemMetrics(SM_CYSCREEN);
		flags = WS_POPUP | WS_VISIBLE | WS_SYSMENU | WS_EX_TOPMOST;
	} else
	{
		width = 650;
		height= 510;

		flags = WS_POPUP | WS_VISIBLE | WS_SYSMENU | WS_BORDER | WS_CAPTION;
	}

	g_hWnd = CreateWindowEx(0, szClassName, szClassName, flags, 0, 0, width, height, 0, 0, hInstance, NULL);
	if (g_hWnd == NULL)
		return 0;

	UpdateWindow(g_hWnd);

	SetCursor(FALSE);

	CreateDirectDraw(4);

	ThreadHandle = _beginthread(WorkerThread, 0xFFFF, 0);

	MSG	msg;
	bool	flag = false;

	while (GetMessage(&msg, 0, 0, 0)) 
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);

		if (dword_579328 == 0)
			flag = true;

		if (flag == true && dword_579328 == 1)
			RestoreSurfaces();

		if (global_QuitFlag == 1)
		{
			CleanupSurfaces();

			DestroyWindow(g_hWnd);

			break;
		}
	}

	return 0;
}